跳到主要内容

行为型模式-观察者模式和发布订阅模式

观察者模式

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。

在观察者模式中,观察者需要直接订阅目标事件;在目标发出内容改变的事件后,直接接收事件并作出响应

 ╭─────────────╮  Fire Event  ╭──────────────╮
│ │─────────────>│ │
│ Subject │ │ Observer │
│ │<─────────────│ │
╰─────────────╯ Subscribe ╰──────────────╯

观察者模式简单实现

注意与下面的订阅发布模式的简单实现对比

class Subject {
constructor() {
this.observers = [];
}

add(observer) {
this.observers.push(observer);
}

notify(...args) {
this.observers.forEach(observer => observer.update(...args));
}
}

class Observer {
update(...args) {
console.log(...args);
}
}

// 创建观察者ob1
let ob1 =new Observer();
// 创建观察者ob2
let ob2 =new Observer();
// 创建目标sub
let sub =new Subject();
// 目标sub添加观察者ob1 (目标和观察者建立了依赖关系)
sub.add(ob1);
// 目标sub添加观察者ob2
sub.add(ob2);
// 目标sub触发SMS事件(目标主动通知观察者)
sub.notify('I fired `SMS` event');

发布订阅模式

其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。

但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。

在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为调度中心或事件通道,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。

举一个例子,你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的(你的关注,A的发布动态)。

 ╭─────────────╮                 ╭───────────────╮   Fire Event   ╭──────────────╮
│ │ Publish Event │ │───────────────>│ │
│ Publisher │────────────────>│ Event Channel │ │ Subscriber │
│ │ │ │<───────────────│ │
╰─────────────╯ ╰───────────────╯ Subscribe ╰──────────────╯

发布订阅模式是最常用的一种观察者模式的实现,并且从解耦和重用角度来看,更优于典型的观察者模式

发布者 (Publisher)当新事件发生时, 发送者会遍历订阅列表并调用每个订阅者对象的通知方法。 订阅者 (Subscriber) 接口声明了通知接口。

订阅发布模式的简单实现

class EventChannel {
constructor() {
this.subscribers = [];
}

subscribe(topic, callback) {
let callbacks =this.subscribers[topic];
if(!callbacks) {
this.subscribers[topic] = [callback];
}else{
callbacks.push(callback);
}
}

publish(topic, ...args) {
letcallbacks =this.subscribers[topic] || [];
callbacks.forEach(callback => callback(...args));
}
}

// 创建事件调度中心,为订阅者和发布者提供调度服务
let eventChannel = new EventChannel();

// A订阅了SMS事件(A只关注SMS本身,而不关心谁发布这个事件)
eventChannel.subscribe('SMS', console.log);
// B订阅了SMS事件
eventChannel.subscribe('SMS', console.log);

// C发布了SMS事件(C只关注SMS本身,不关心谁订阅了这个事件)
eventChannel.publish('SMS','I published `SMS` event');

从代码实现可以看出,发布-订阅模式是面向调度中心编程的,而观察者模式则是面向目标和观察者编程的。前者用于解耦发布者和订阅者,后者用于耦合目标和观察者,不可同日而语也~

Java 编辑器实例

参考资料 Java 观察者模式讲解和代码示例

在本例中,观察者模式在文本编辑器的对象之间建立了间接的合作关系。每当编辑器 (Editor)对象改变时,它都会通知其订阅者。然后这些监听器会执行相应的操作

邮件通知监听器 (Email­Notification­Listener) 日志开启监听器 (Log­Open­Listener)


publisher/EventManager.java: 基础发布者(这里将通知和编辑器进行了解耦)

public class EventManager {
Map<String, List<EventListener>> listeners = new HashMap<>();

// 需要监听的操作(这里的 ArrayList 表示一种操作可能有多个订阅者)
public EventManager(String... operations) {
for (String operation : operations) {
this.listeners.put(operation, new ArrayList<>());
}
}
// 这里需要声明需要监听的操作类型
public void subscribe(String eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.add(listener);
}

public void unsubscribe(String eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.remove(listener);
}

public void notify(String eventType, File file) {
List<EventListener> users = listeners.get(eventType);
for (EventListener listener : users) {
listener.update(eventType, file);
}
}
}

editor/Editor.java: 具体发布者

public class Editor {
public EventManager events;
private File file;

public Editor() {
// 初始化声明需要监听 "open", "save" 这两种操作
this.events = new EventManager("open", "save");
}

public void openFile(String filePath) {
this.file = new File(filePath);
events.notify("open", file);
}

public void saveFile() throws Exception {
if (this.file != null) {
events.notify("save", file);
} else {
throw new Exception("Please open a file first.");
}
}
}

listeners/EventListener.java: 通用观察者接口

public interface EventListener {
void update(String eventType, File file);
}

listeners/EmailNotificationListener.java: 收到通知后发送邮件

public class EmailNotificationListener implements EventListener {
private String email;

public EmailNotificationListener(String email) {
this.email = email;
}

@Override
public void update(String eventType, File file) {
System.out.println(
"Email to " + email + ": Someone has performed " +
eventType + " operation with the following file: " +
file.getName());
}
}

listeners/LogOpenListener.java: 收到通知后在日志中记录一条消息

public class LogOpenListener implements EventListener {
private File log;

public LogOpenListener(String fileName) {
this.log = new File(fileName);
}

@Override
public void update(String eventType, File file) {
System.out.println(
"Save to log " + log + ": Someone has performed "
+ eventType + " operation with the following file: "
+ file.getName());
}
}

Demo.java: 初始化代码

public class Demo {
public static void main(String[] args) {
Editor editor = new Editor();
editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt"));
editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));

try {
editor.openFile("test.txt");
editor.saveFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}

C# 的事件委托机制

因为不可能让所有的类都去实现 Subscriber 接口,所有 C# 提供一种无需实现 Subscriber 接口的 “观察者” 模式,事件委托机制,就是在 “事件” 来的时候调用被 “委托” 的方法

委托就是一种引用方法的类型。一但为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其它任何方法一样,具有参数和返回值。所有说委托可以看作是对函数的抽象,委托的实例则代表一个具体的函数

但是注意:委托对象所搭载的所有方法必须具有相同的原型和形式(就是拥有相同的参数列表和返回值类型)

如下创建两个自定义的需要被通知的类

class SubscriberA {
private Publisher publisher;

public SubscriberA(Publisher publisher) {
this.publisher = publisher;
}

public void notifyA() {
Console.WriteLine("这是被通知的类 A,当前 publisher 的状态为 {0}", publisher.publisherState)
}
}

// 注意:这里的 notifyA 与 notifyB 方法名是不一样的
class SubscriberB {
private Publisher publisher;

public SubscriberB(Publisher publisher) {
this.publisher = publisher;
}

public void notifyB() {
Console.WriteLine("这是被通知的类 B,当前 publisher 的状态为 {0}", publisher.publisherState)
}
}

定义通知者接口

// 通知者接口
interface Publisher {
void notify();
string publisherState {
get;
set;
}
}

声明一个委托

// 把委托放在命名空间外(就是全局定义)
delegate void EventHandler();

定义具体的通知类

class ConcretePublisherA : Publisher {
// 声明一个事件 update,类型为委托 EventHandler
public event EventHandler update;
private string action;

public void notify() {
update(); // 调用这个 notify 方法时,调用 update 事件
}

public string publisherState {
get { return action; }
set { action = value; }
}
}

Client 代码

ConcretePublisherA publisher = new ConcretePublisherA();
// 声明被通知的类
SubscriberA a = new SubscriberA(publisher);
SubscriberB b = new SubscriberB(publisher);

// 把这两个需要被通知的类的方法注册进委托里面
publisher.update += new EventHandler(a.notifyA);
publisher.update += new EventHandler(b.notifyB);

// 变更状态,发起通知
publisher.publisherState = "这是新的状态";
publisher.notify();

// 打印出来的结果是:
// 这是被通知的类 A,当前 publisher 的状态为 这是新的状态
// 这是被通知的类 B,当前 publisher 的状态为 这是新的状态

Reference

参考资料 观察者模式与订阅发布模式的区别